use std::time;

use anyhow::Ok;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use uuid::Uuid;

use crate::{crypto, error::BizError};

pub trait StringUtils {
    fn is_not_blank(&self) -> bool;
}
impl StringUtils for Option<String> {
    fn is_not_blank(&self) -> bool {
        self.is_some() && self.as_ref().unwrap().trim().len() > 0
    }
}

impl StringUtils for String {
    fn is_not_blank(&self) -> bool {
        self.trim().len() > 0
    }
}

impl StringUtils for &str {
    fn is_not_blank(&self) -> bool {
        self.trim().len() > 0
    }
}

pub struct UUID;
impl UUID {
    pub fn generate() -> String {
        let ss = Uuid::new_v4().to_string().replace("-", "0");
        ss
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TokenPayload {
    pub userid: String,
    pub sessionid: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Token {
    pub timestamp: i64,
    pub payload: TokenPayload,
}

impl Token {
    pub fn generate(key: &str, nonce: &str, payload: &TokenPayload) -> anyhow::Result<String> {
        let utc_now: DateTime<Utc> = Utc::now();
        let timestamp = utc_now.timestamp();
        let token = Token {
            timestamp,
            payload: payload.clone(),
        };

        let json = serde_json::to_string(&token)?;
        let token = crypto::aes_encrypt_hex(json.as_str(), key, nonce)?;

        Ok(token)
    }

    pub fn generate_env(payload: &TokenPayload) -> anyhow::Result<String> {
        dotenvy::dotenv().expect("没有.env文件");

        let token_gen_key = std::env::var("TOKEN_GEN_KEY").expect("TOKEN_GEN_KEY must be set");

        let token_gen_nonce =
            std::env::var("TOKEN_GEN_NONCE").expect("TOKEN_GEN_NONCE must be set");

        Self::generate(token_gen_key.as_str(), token_gen_nonce.as_str(), payload)
    }

    pub fn parse(key: &str, nonce: &str, token: &str, expired_time: i64) -> anyhow::Result<Token> {
        let json = crypto::aes_decrypt_hex(token, key, nonce)?;

        let token: Token = serde_json::from_str(&json)?;
        let utc_now: DateTime<Utc> = Utc::now();
        let timestamp = utc_now.timestamp();
        let elapsed = timestamp - token.timestamp;
        if elapsed > expired_time {
            anyhow::bail!(BizError::TokenExpired);
        }

        // TODO  校验过期时间

        Ok(token)
    }

    pub fn parse_env(token: &str) -> anyhow::Result<Token> {
        dotenvy::dotenv().expect("没有.env文件");

        let token_gen_key = std::env::var("TOKEN_GEN_KEY").expect("TOKEN_GEN_KEY must be set");

        let expired_time =
            std::env::var("TOKEN_EXPIRE_TIME").expect("TOKEN_EXPIRE_TIME must be set");

        let expired_time = expired_time.parse::<i64>()?;

        let token_gen_nonce =
            std::env::var("TOKEN_GEN_NONCE").expect("TOKEN_GEN_NONCE must be set");

        Self::parse(
            token_gen_key.as_str(),
            token_gen_nonce.as_str(),
            token,
            expired_time,
        )
    }
}

#[macro_export]
macro_rules! is_not_blank_valitor {
    ($( $field:expr, $field_name: expr, $msg:expr ) ; *) => {{
        let mut errors = ::validify::ValidationErrors::new();
        $(
            if !axum_app_common::utils::StringUtils::is_not_blank($field) {
                errors.add(validify::ValidationError::new_field($field_name, "IS_BLANK").with_message($msg));
            }
        )*
        errors
    }};
}

#[cfg(test)]
mod tests {
    use serde_json::json;

    use crate::utils::{Token, TokenPayload, UUID};

    const key: &str = "1234567890123456";
    const nonce: &str = "1234567890123456";

    #[test]
    fn test_generate_uuid() {
        println!("{}", UUID::generate());
    }

    #[test]
    fn test_generate_token() {
        let token_payload = TokenPayload {
            userid: "123456".to_string(),
            sessionid: "".to_string(),
        };

        let token = Token::generate(key, nonce, &token_payload).unwrap();
        println!("{}", token)
    }

    #[test]
    fn test_parse_token() {
        let token = "08af3b5a84354a545647e31f93030d1c6f96fdd63756c1ab7185eacaa7939f5d2a5c5be4012c35823cd8bcb0186fef1152d08f4d9f8f0d6733dcad5687096a857249bd0b684a";

        let json_val = Token::parse(key, nonce, token, -1).unwrap();
        println!("val:{:?}", json_val);
    }
}
